package org.jdsm.flow.core.utils.impl.jpa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: mikom
 * Date: 9/12/11
 * Time: 11:41 AM
 */
public class PersistenceUtils {
private static final Logger log = LoggerFactory.getLogger(PersistenceUtils.class);

    public static <X> List<X> emptyList(){
        return new ArrayList<X>();
    }

    public static <X> Long count(EntityManager em, Class<X> clazz) {
        if (em == null) {
            log.error("Entity manager can't be null");
            return 0l;
        }
        if (clazz == null) {
            log.error("Class can't be null");
            return 0l;
        }
        log.debug("count entity by class: {}", clazz.getName());
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
        Root<X> rt = criteriaQuery.from(clazz);
        criteriaQuery.select(criteriaBuilder.count(rt));
        Query q = em.createQuery(criteriaQuery);
        Long result = (Long) q.getSingleResult();
        log.debug("entities equally: {}", result);
        return result;
    }

    /**
     * method load entities by name(only for IRole, IGroup...)
     * @param em  entity manager
     * @param clazz class type
     * @param names field names
     * @param <X> result type of class type
     * @return result list
     */
    public static <X> List<X> loadEntitiesByName(EntityManager em, Class<X> clazz, Collection<String> names){
        if (em == null) {
            log.error("Entity manager can't be null");
            return null;
        }
        if (clazz == null) {
            log.error("Class can't be null");
            return null;
        }

        log.debug("load entities:{} by names:{}", clazz.getName(), names);
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<X> criteriaQuery = criteriaBuilder.createQuery(clazz);
        Root<X> rPath = criteriaQuery.from(clazz);


        criteriaQuery.where(rPath.get("name").in(names));
        List<X> resultList = em.createQuery(criteriaQuery).getResultList();
        log.debug("result: {}", resultList);
        return resultList;
    }
    /**
     * method load entities by name(only for IRole, IGroup...)
     * @param em  entity manager
     * @param clazz class type
     * @param ids field names
     * @param <X> result type of class type
     * @return result list
     */
    public static <X> List<X> loadEntitiesByIds(EntityManager em, Class<X> clazz, Collection<Long> ids) {
        if (em == null) {
            log.error("Entity manager can't be null");
            return null;
        }
        if (clazz == null) {
            log.error("Class can't be null");
            return null;
        }

        log.debug("load entities:{} by ids:{}", clazz.getName(), ids);
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<X> criteriaQuery = criteriaBuilder.createQuery(clazz);
        Root<X> rPath = criteriaQuery.from(clazz);


        criteriaQuery.where(rPath.get("id").in(ids));
        List<X> resultList = em.createQuery(criteriaQuery).getResultList();
        log.debug("result: {}", resultList);
        return resultList;

    }

    public static <X> List<X> loadedByCriteria(EntityManager em, Class<X> clazz, int maxResult,
                                               int startResult, boolean isSort, String field) {
        if (em == null) {
            log.error("Entity manager can't be null");
            return null;
        }
        if (clazz == null) {
            log.error("Class can't be null");
            return null;
        }
        log.debug("load data by criteria# maxResult:{}, start:{}, isSort:{}, sorted field:{}",
                new Object[]{maxResult, startResult, isSort, field});

        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<X> criteriaQuery = criteriaBuilder.createQuery(clazz);
        Root<X> root = criteriaQuery.from(clazz);

        Path<X> dataPath = null;

        for (Attribute att : root.getModel().getAttributes()) {
            if (att.getName().equals(field)) {
                dataPath = root.get(field);
            }
        }
        if (dataPath != null)
            if (isSort) {
                criteriaQuery.orderBy(criteriaBuilder.asc(dataPath));
            } else {
                criteriaQuery.orderBy(criteriaBuilder.desc(dataPath));
            }

        TypedQuery<X> typedQuery = em.createQuery(criteriaQuery);
        typedQuery.setMaxResults(maxResult);
        typedQuery.setFirstResult(startResult);
        List<X> results = typedQuery.getResultList();
        if (log.isDebugEnabled()) {
            for (X obj : results) {
                log.debug("was loaded data obj: {}", obj);
            }
        }
        return results;
    }

    public static <X,Y> List<X> loadChildByCriteria(EntityManager em, Y parent, Class<X> clazz, String childAttributeName,
                                            int maxResult, int startResult, boolean isSort, String field){

        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<X> criteriaQuery = criteriaBuilder.createQuery(clazz);

        Class<Y> rootClass = (Class<Y>) parent.getClass();
        Root<Y> root = criteriaQuery.from(rootClass);

        Path<X> dataPath = root.get(childAttributeName);
        criteriaQuery.select(dataPath);
        criteriaQuery.where(criteriaBuilder.equal(root, parent));

        Path<X> orderPath = null;

        for (Attribute att : root.getModel().getAttributes()) {
            if (att.getName().equals(field)) {
                orderPath = root.get(field);
                break;
            }
        }
        if (orderPath != null)
            if (isSort) {
                criteriaQuery.orderBy(criteriaBuilder.asc(orderPath));
            } else {
                criteriaQuery.orderBy(criteriaBuilder.desc(orderPath));
            }

        TypedQuery<X> typedQuery = em.createQuery(criteriaQuery);
        typedQuery.setMaxResults(maxResult);
        typedQuery.setFirstResult(startResult);
        List<X> results = typedQuery.getResultList();
        if (log.isDebugEnabled()) {
            for (X obj : results) {
                log.debug("was loaded data obj: {}", obj);
            }
        }
        return results;
    }
}
